package code.whmmm.util.spring.aspect.handler;

import code.whmmm.util.spring.aspect.annotation.AspectType;
import code.whmmm.util.spring.aspect.annotation.AspectWrapper;
import code.whmmm.util.spring.aspect.annotation.CodeAspect;
import code.whmmm.util.spring.aspect.annotation.CodeAspects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import code.whmmm.util.spring.aspect.util.AspectUtil;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 默认的 aspect 处理器
 * <br/> -- 创建时间: 2019/12/23 12:31
 *
 * @author Zero
 */
@Aspect
@Order(1)
@Component
public final class AspectHandlerResolver {

    private final static Logger log = LoggerFactory.getLogger(AspectHandler.class);

    private static final Map<Class<? extends AspectHandler>, AspectHandler> MAP
            = new HashMap<Class<? extends AspectHandler>, AspectHandler>(256);

    /**
     * 此方法只在初始化时执行。
     *
     * @param list 处理集合
     */
    @Autowired(required = false)
    private void initializeMap(List<AspectHandler> list) {
        for (AspectHandler i : list) {
            MAP.put(i.getClass(), i);
        }
    }


    /**
     * 使用自定义注解时，可以使用此方法。 <br/>
     * 当自定义注解包含 @{@link CodeAspect} 时，调用此方法。
     *
     * @param point 连接点
     * @param clazz 注解
     * @return 连接点 方法返回值
     * @throws Throwable 异常
     */
    public Object resolve(ProceedingJoinPoint point, Class<? extends Annotation> clazz) throws Throwable {
        CodeAspect aspect = clazz.getAnnotation(CodeAspect.class);
        if (aspect == null) {
            // 不调用参数。
            return point.proceed();
        }
        return this.aspectHandler(point, new CodeAspect[]{aspect});
    }

    /**
     * 注解 {@link CodeAspects} 的切点.<br/>
     * 当自定义注解 含有  @{@link CodeAspects} 注解时，调用此方法。
     *
     * @param point 连接点
     * @return 返回值
     * @throws Throwable 异常
     */
    public Object resolveAspectArray(ProceedingJoinPoint point, Class<? extends Annotation> clazz) throws Throwable {
        CodeAspects aspects = clazz.getAnnotation(CodeAspects.class);
        if (aspects == null || aspects.value().length == 0) {
            // 不调用参数。
            return point.proceed();
        }
        return this.aspectHandler(point, aspects.value());
    }

    @Around("@annotation(code.whmmm.util.spring.aspect.annotation.CodeAspect)"
            + "|| @within(code.whmmm.util.spring.aspect.annotation.CodeAspect)"
    )
    protected Object around(ProceedingJoinPoint point) throws Throwable {
        CodeAspect aspect = AspectUtil.getAnnotation(point);
        if (aspect == null) {
            return point.proceed();
        }

        return this.aspectHandler(point, new CodeAspect[]{aspect});
    }

    @Around("@annotation(code.whmmm.util.spring.aspect.annotation.CodeAspects)"
            + "|| @within(code.whmmm.util.spring.aspect.annotation.CodeAspects)"
    )
    protected Object aroundArray(ProceedingJoinPoint point) throws Throwable {
        CodeAspects aspects = AspectUtil.getAnnotation(point, CodeAspects.class);
        if (aspects == null || aspects.value().length == 0) {
            return point.proceed();
        }

        return this.aspectHandler(point, aspects.value());
    }

    @Around("@annotation(code.whmmm.util.spring.aspect.annotation.AspectWrapper)"
            + "|| @within(code.whmmm.util.spring.aspect.annotation.AspectWrapper)"
    )
    protected Object aroundWrapper(ProceedingJoinPoint point) throws Throwable {

        AspectWrapper annotation = AspectUtil.getAnnotation(point, AspectWrapper.class);
        CodeAspect aspect = annotation.value().getAnnotation(CodeAspect.class);
        if (aspect == null) {
            // 不调用参数。
            return point.proceed();
        }
        return this.aspectHandler(point, new CodeAspect[]{aspect});
    }


    private static final Map<String, AspectHandlerChain> cache =
            new ConcurrentHashMap<String, AspectHandlerChain>();

    /**
     * 切面具体处理函数。
     *
     * @param point 连接点
     * @param info  {@link CodeAspect[]}
     * @return 返回值
     * @throws Exception 异常
     */
    protected Object aspectHandler(ProceedingJoinPoint point, CodeAspect[] info) throws Exception {

        String cacheKey = getCacheKey(info);

        boolean hasCache = false;
        List<AspectHandler> beforeList = null;
        List<AspectHandler> afterList = null;
        List<AspectHandler> throwList = null;
        AspectHandlerChain cacheMap = cache.get(cacheKey);
        if (cacheMap == null) {
            beforeList = new ArrayList<AspectHandler>();
            afterList = new ArrayList<AspectHandler>();
            throwList = new ArrayList<AspectHandler>();
        } else {
            hasCache = true;

            beforeList = cacheMap.getBeforeList();
            afterList = cacheMap.getAfterList();
            throwList = cacheMap.getThrowList();
        }


        try {

            Object methodResult;

            if (!hasCache) {
                buildHandlerList(beforeList, afterList, throwList, info);
                AspectHandlerChain m = new AspectHandlerChain();
                m.setBeforeList(beforeList);
                m.setAfterList(afterList);
                m.setThrowList(throwList);
                cache.put(cacheKey, m);
            }

            boolean b = this.handleBefore(beforeList, point);


            if (!b) {
                // doBefore 返回为 false,不执行 proceed。
                return null;
            }

            methodResult = point.proceed(point.getArgs());

            methodResult = this.handleAfter(afterList, point, methodResult);

            return methodResult;
        } catch (Throwable e) {
            boolean b = this.handleThrow(throwList, point, new Exception(e));
            if (b) {
                throw new Exception(e);
            }
            return null;
        }
    }

    public static AspectHandlerChain buildChain(CodeAspect[] array) {
        AspectHandlerChain chain = new AspectHandlerChain();
        List<AspectHandler> beforeList = new ArrayList<AspectHandler>();
        List<AspectHandler> afterList = new ArrayList<AspectHandler>();
        List<AspectHandler> throwList = new ArrayList<AspectHandler>();
        buildHandlerList(beforeList, afterList, throwList, array);
        chain.setBeforeList(beforeList);
        chain.setAfterList(afterList);
        chain.setThrowList(throwList);
        return chain;
    }

    private static void buildHandlerList(List<AspectHandler> beforeList,
                                         List<AspectHandler> afterList,
                                         List<AspectHandler> throwList,
                                         CodeAspect[] array) {
        // 根据 注解信息，构建 处理集合
        for (CodeAspect aspect : array) {

            if (aspect == null) {
                continue;
            }

            Class<? extends AspectHandler>[] handlers = aspect.handles();
            if (handlers.length == 0) {
                return;
            }

            int type = aspect.type();

            if (type == 0 || type == 6) {
                // 全部的情况
                addToList(handlers, beforeList, afterList, throwList);
            } else {
                // 任意两种组合。
                int[] sum = AspectType.getTowSum(type);
                if (sum != null) {
                    for (int i : sum) {
                        switch (i) {
                            case 1:
                                addToList(handlers, beforeList);
                                break;
                            case 2:
                                addToList(handlers, afterList);
                                break;
                            case 4:
                                addToList(handlers, throwList);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
        }
    }



    private static void addToList(Class<? extends AspectHandler>[] handlers,
                                  List<AspectHandler>... list) {

        for (Class<? extends AspectHandler> it : handlers) {
            for (List<AspectHandler> h : list) {
                AspectHandler handler = MAP.get(it);
                if (handler != null) {
                    h.add(handler);
                }
            }
        }


    }

    private boolean handleBefore(List<AspectHandler> list, ProceedingJoinPoint point) {
        boolean b;
        // 执行 doBefore 前置通知。
        for (AspectHandler it : list) {
            b = it.doBefore(point);
            if (!b) {
                return false;
            }
        }
        return true;
    }

    private Object handleAfter(List<AspectHandler> list,
                               ProceedingJoinPoint point,
                               Object o) {

        for (AspectHandler a : list) {
            o = a.doAfter(point, o);
        }

        return o;
    }

    private boolean handleThrow(List<AspectHandler> list,
                                ProceedingJoinPoint point,
                                Exception e) {
        boolean b = true;
        for (AspectHandler a : list) {
            b = a.doException(point, e);
        }
        return b;
    }


    private String getCacheKey(CodeAspect[] info) {
        StringBuilder sb = new StringBuilder();
        for (CodeAspect i : info) {
            for (Class<? extends AspectHandler> handle : i.handles()) {
                if (sb.length() != 0) {
                    sb.append('|');
                } else {
                    sb.append(handle.getName());
                }
            }
            sb.append("||").append(i.type());
        }
        return sb.toString();
    }
}
